캐싱 전략

  • 캐시 적중 : 캐시에 접근했을 때 찾고 있는 데이터가 있는 경우
  • 캐시 누락 : 캐시에 접근했을 때 찾고 있는 데이터가 없는 경우
  • 삭제 정책 : 캐시에 공간이 부족할 때 공간을 확보하는 방법

Cache-Aside

  • Lazy Loading, 데이터를 조회할 때 항상 캐시를 먼저 확인하는 전략
  • 필요 데이터만 캐시에 보관
  • 최초 조회시 캐시 확인을 위해 시간이 상대적으로 오래 걸림

Write-Through

  • 데이터 작성 시 항상 캐시에 작성하고 원본에도 작성하는 전략
  • 캐시의 데이터 상태가 항상 최신임을 보장
  • 자주 사용하지 않는 데이터도 캐시에 중복해서 저장 시간이 오래 걸림

Write-Behind

  • 캐시에만 데이터를 작성하고 일정 주기로 원본을 갱신하는 방식
  • 캐시 데이터가 원본에 적용하기 전 문제가 발생하면 데이터 소실될 위험성 O

Spring Cache

CacheConfig

@Configuration
@EnableCaching
public class CacheConfig {
 
    @Bean
    // CacheManager로 진행해도 정상 동작
    public RedisCacheManager cacheManager(
            RedisConnectionFactory redisConnectionFactory
    ) {
        // 설정 구성을 먼저 진행한다.
        // Redis를 이용해서 Spring Cache를 사용할 때
        // Redis 관련 설정을 모아두는 클래스
        RedisCacheConfiguration configuration = RedisCacheConfiguration
                .defaultCacheConfig()
                // null을 캐싱 할것인지
                .disableCachingNullValues()
                // 기본 캐시 유지 시간 (Time To Live)
                .entryTtl(Duration.ofSeconds(10))
                // 캐시를 구분하는 접두사 설정
                .computePrefixWith(CacheKeyPrefix.simple())
                // 캐시에 저장할 값을 어떻게 직렬화 / 역직렬화 할것인지
                .serializeValuesWith(
                        SerializationPair.fromSerializer(RedisSerializer.java())
                );
                
        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(configuration)
                .build();
    }
}

@EnableCaching

// cacheNames: 메서드로 인해서 만들어질 캐시를 지칭하는 이름
// key: 캐시에서 데이터를 구분하기 위해 활용할 값
@Cacheable(cacheNames = "itemCache", key = "args[0]")
public ItemDto readOne(Long id) {
    log.info("Read One: {}", id);
    return repository.findById(id)
            .map(ItemDto::fromEntity)
            .orElseThrow(() 
                    -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

@CachePut

@CachePut(cacheNames = "itemCache", key = "#result.id")
public ItemDto create(ItemDto dto) {
    return ItemDto.fromEntity(itemRepository.save(Item.builder()
            .name(dto.getName())
            .description(dto.getDescription())
            .price(dto.getPrice())
            .stock(dto.getStock())
            .build()
    ));
}

@CacheEvict

@CachePut(cacheNames = "itemCache", key = "args[0]")
@CacheEvict(cacheNames = "itemAllCache", allEntries = true)
public ItemDto update(Long id, ItemDto dto) {
    Item item = itemRepository.findById(id)
                    .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
    item.setName(dto.getName());
    item.setDescription(dto.getDescription());
    item.setPrice(dto.getPrice());
    item.setStock(dto.getStock());
    return ItemDto.fromEntity(itemRepository.save(item));
}